﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Web.Mvc;

using Sencha.Direct;
using Sencha.Serialization;
using System.Web.Mvc.Async;

namespace Sencha.Direct.Api
{
	public class ActionDescription
	{
		private ActionDescriptor m_descriptor;

		public ActionDescriptor Descriptor { get { return m_descriptor; } }
		public string Name { get; set; }
		public ICollection<ParameterDescription> Parameters { get; private set; }
		public Type ReturnType { get; private set; }
		public CacheParameters Caching { get; private set; }
		public ICollection<string> AcceptVerbs { get; private set; }
		public int TimeoutInMilliseconds { get; private set; }

		public bool IsFormRequest { get; private set; }
		public bool IsDirectOnly { get; private set; }
		public bool IsParallel { get; private set; }
		public bool IsJsonP { get; private set; }
		public bool IsIgnored { get; private set; }
		public bool IsAsync { get; private set; }
		public bool IsInputValidated { get; private set; }
		public bool IsChildActionOnly { get; private set; }

		public ActionDescription(ControllerDescription controllerDescription, ActionDescriptor actionDescriptor)
		{
			if (controllerDescription == null)
				throw new ArgumentNullException("controllerDescription");
			if (actionDescriptor == null)
				throw new ArgumentNullException("actionDescriptor");

			m_descriptor = actionDescriptor;

			this.Name = actionDescriptor.GetAttributeValue<ActionNameAttribute, string>(true, a => a.Name) ??
				actionDescriptor.ActionName;

			this.IsParallel = controllerDescription.IsParallel || AllowParallelAttribute.IsDefined(actionDescriptor);
			this.IsDirectOnly = DirectOnlyAttribute.IsDefined(actionDescriptor) || controllerDescription.IsDirectOnly;
			this.IsJsonP = AllowJsonPAttribute.IsDefined(actionDescriptor) || controllerDescription.IsJsonP;
			this.IsFormRequest = FormRequestAttribute.IsDefined(actionDescriptor);
			this.IsChildActionOnly = actionDescriptor.IsDefined<ChildActionOnlyAttribute>(true) || controllerDescription.IsChildActionOnly;
			this.IsInputValidated = actionDescriptor.IsDefined<ValidateInputAttribute>(true) || controllerDescription.IsInputValidated;
			this.IsIgnored = actionDescriptor.IsDefined<NonDirectActionAttribute>(true) || actionDescriptor.IsDefined<NonActionAttribute>(true) || controllerDescription.IsNonDirect;

			this.Caching = actionDescriptor.GetAttributeValue<OutputCacheAttribute, CacheParameters>(true, a => new CacheParameters(a)) ??
					controllerDescription.Caching;

			this.TimeoutInMilliseconds = actionDescriptor.GetAttributeValue<AsyncTimeoutAttribute, int?>(true, a => a.Duration) ??
				actionDescriptor.GetAttributeValue<NoAsyncTimeoutAttribute, int?>(true, a => a.Duration) ?? controllerDescription.TimeoutInMilliseconds;

			this.AcceptVerbs = actionDescriptor.GetAttributeValue<AcceptVerbsAttribute, ICollection<string>>(true, a => a.Verbs) ??
				actionDescriptor.GetAttributeValue<HttpGetAttribute, string[]>(true, a => new string[] { "GET" }) ??
				actionDescriptor.GetAttributeValue<HttpPostAttribute, string[]>(true, a => new string[] { "POST" }) ??
				actionDescriptor.GetAttributeValue<HttpPutAttribute, string[]>(true, a => new string[] { "PUT" }) ??
				actionDescriptor.GetAttributeValue<HttpDeleteAttribute, string[]>(true, a => new string[] { "DELETE" });

			if (actionDescriptor is ReflectedActionDescriptor)
				this.ReturnType = (actionDescriptor as ReflectedActionDescriptor).MethodInfo.ReturnType;
			else if (actionDescriptor is ReflectedAsyncActionDescriptor)
				this.ReturnType = (actionDescriptor as ReflectedAsyncActionDescriptor).CompletedMethodInfo.ReturnType;
			else
				this.ReturnType = null;

			var parameters = actionDescriptor.GetParameters();
			var parametersList = new List<ParameterDescription>(parameters.Length);
			for (int i = 0; i < parameters.Length; i++)
				parametersList.Add(new ParameterDescription(parameters[i]));

			this.Parameters = parametersList.AsReadOnly();
		}

		internal void SerializeTo(IJsonWriter writer)
		{
			writer.WriteObjectBegin();
			writer.WriteMember("name", this.Name);
			writer.WriteMember("len", this.Parameters.Count);
            writer.WriteMember("params");
			{
				writer.WriteArrayBegin();
				foreach (var parameter in this.Parameters)
				{
					writer.WriteString(parameter.Name);
				}
				writer.WriteArrayEnd();
			}
			writer.WriteMember("formHandler", IsFormRequest);
			writer.WriteObjectEnd();
		}

		public override string ToString()
		{
			return string.Format("{0} {1}", this.ReturnType, this.Name);
		}
	}
}
